#include "code_comment_info.h"

#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FileUtilities.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/raw_ostream.h"

#include <algorithm>

std::string CodeCommentInfoTypeStr[] = {
    "", 
    #undef  CODE_COMMENT_TYPE_KV
    #define CODE_COMMENT_TYPE_KV(x, y) y,
    #include "code_comment_type.def"
};

void CodeCommentInfoNode::toJson(llvm::json::Object& obj)
{
    llvm::json::Object newObj;
    std::string cmtInfoKeyStr = "flow";
    if (NULL_COMMENT != mCommentType) {
        cmtInfoKeyStr = CodeCommentInfoTypeStr[mCommentType];
    }

    for(auto iter = mKeyInfo.begin(); iter != mKeyInfo.end(); iter++) {
        newObj.insert({iter->first, iter->second});
    }
    if(mChildren.empty()) {
        obj.insert({cmtInfoKeyStr, std::move(newObj)});
        return;
    }
    llvm::json::Array subArray;
    for(auto nodePtr : mChildren) {
        if(nodePtr) {
            llvm::json::Object subObj;
            nodePtr->toJson(subObj);
            subArray.push_back(std::move(subObj));
        }
    }
    newObj.insert({"flow", std::move(subArray)});
    obj.insert({cmtInfoKeyStr, std::move(newObj)});
}
void CodeCommentInfo::addInfo(CodeCommentInfoNode* cmtNodePtr)
{
    if(nullptr == mCurrentNodePtr ||
        nullptr == cmtNodePtr) {
        return;
    }
    mCurrentNodePtr->mChildren.push_back(cmtNodePtr);
}

void CodeCommentInfo::pushInfo(CodeCommentInfoNode* cmtNodePtr)
{
    addInfo(cmtNodePtr);
    cmtNodePtr->mParent = mCurrentNodePtr;
    mCurrentNodePtr = cmtNodePtr;
}

void CodeCommentInfo::pop()
{
    mCurrentNodePtr = mCurrentNodePtr->mParent;
    if (nullptr == mCurrentNodePtr) {
        mCurrentNodePtr = &mRootNode;
    }
}

bool CodeCommentInfo::toJson(llvm::json::Object& obj)
{
    mRootNode.toJson(obj);
    return true;
}


CodeCommentInfo* CodeCommentInfoManager::getOrAddCodeCommentInfo(std::string ident, std::string funcKey)
{
    /**
     *  here if there is an entry in mCodeCommentInfoForFunc, 
     *  it means this function has been visited. we do not visit it again
     */
    CodeCommentInfo* newNode = nullptr;
    mRegisterMutex.lock();
    if(registerFuncInfo(ident, funcKey)) {
        if (mCodeCommentInfoMap.end() == mCodeCommentInfoMap.find(ident)) {
            newNode = new CodeCommentInfo();
            if (nullptr != newNode) {
                mCodeCommentInfoMap[ident] = newNode;
            }
        }
    }
    mRegisterMutex.unlock();
    return newNode;
}


bool CodeCommentInfoManager::registerFuncInfo(std::string ident, std::string funcName)
{
    if(mCodeCommentInfoForFunc.end() == mCodeCommentInfoForFunc.find(ident)) {
        mCodeCommentInfoForFunc.insert(std::pair<std::string, std::set<std::string>>(ident, {funcName}));
        return true;
    }
    if(mCodeCommentInfoForFunc[ident].end() != mCodeCommentInfoForFunc[ident].find(funcName)) {
        return false;
    }
    mCodeCommentInfoForFunc[ident].insert(funcName);
    return true;
}

bool CodeCommentInfoManager::doWriteOut()
{
    llvm::json::Object jsonObj;
    for(auto iter = mCodeCommentInfoMap.begin(); iter != mCodeCommentInfoMap.end(); iter++) {
        llvm::json::Object subObj;
        if(iter->second) {
            iter->second->toJson(subObj);
            std::string realFileName = getRealFileIdent(iter->first);
            jsonObj.insert({realFileName, std::move(subObj)});
        }
    }
    std::error_code EC;
    llvm::raw_fd_ostream ss(mOutFile, EC, llvm::sys::fs::OF_Text);
    if(EC) {
        llvm::errs() << EC.message() << "\n";
        return false;
    }

    ss << llvm::json::Value(std::move(jsonObj));
    ss.close();
    return true;
}

std::string CodeCommentInfoManager::getRealFileIdent(const std::string& ident)
{
    std::string suffixs[] = {".tmp.h", ".tmp.cpp"};

    for(auto su : suffixs) {
        if (ident.length() > su.length()) {
            auto index = ident.rfind(su);
            if (index == (ident.length() - su.length())) {
                return ident.substr(0, index);
            }
        }
    }
    return ident;
}
